/*
 * Copyright 2023 Google LLC
 *
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 */

#ifndef skgpu_BlendFormula_DEFINED
#define skgpu_BlendFormula_DEFINED

#include "include/private/base/SkAssert.h"
#include "include/private/base/SkMacros.h"
#include "include/private/base/SkTo.h"
#include "src/gpu/Blend.h"

namespace skgpu {
/* *
 * Wraps the shader outputs and HW blend state that comprise a Porter Duff blend mode with coverage.
 */
class BlendFormula {
public:
    /* *
     * Values the shader can write to primary and secondary outputs. These are all modulated by
     * coverage. We will ignore the multiplies when not using coverage.
     */
    enum OutputType {
        kNone_OutputType,        // <! 0
        kCoverage_OutputType,    // <! inputCoverage
        kModulate_OutputType,    // <! inputColor * inputCoverage
        kSAModulate_OutputType,  // <! inputColor.a * inputCoverage
        kISAModulate_OutputType, // <! (1 - inputColor.a) * inputCoverage
        kISCModulate_OutputType, // <! (1 - inputColor) * inputCoverage

        kLast_OutputType = kISCModulate_OutputType
    };

    constexpr BlendFormula(OutputType primaryOut, OutputType secondaryOut, skgpu::BlendEquation equation,
        skgpu::BlendCoeff srcCoeff, skgpu::BlendCoeff dstCoeff)
        : fPrimaryOutputType(primaryOut),
          fSecondaryOutputType(secondaryOut),
          fBlendEquation(SkTo<uint8_t>(equation)),
          fSrcCoeff(SkTo<uint8_t>(srcCoeff)),
          fDstCoeff(SkTo<uint8_t>(dstCoeff)),
          fProps(GetProperties(primaryOut, secondaryOut, equation, srcCoeff, dstCoeff))
    {}

    BlendFormula(const BlendFormula &) = default;
    BlendFormula &operator = (const BlendFormula &) = default;

    bool operator == (const BlendFormula &that) const
    {
        return fPrimaryOutputType == that.fPrimaryOutputType && fSecondaryOutputType == that.fSecondaryOutputType &&
            fBlendEquation == that.fBlendEquation && fSrcCoeff == that.fSrcCoeff && fDstCoeff == that.fDstCoeff &&
            fProps == that.fProps;
    }

    bool hasSecondaryOutput() const
    {
        return kNone_OutputType != fSecondaryOutputType;
    }
    bool modifiesDst() const
    {
        return SkToBool(fProps & kModifiesDst_Property);
    }
    bool unaffectedByDst() const
    {
        return SkToBool(fProps & kUnaffectedByDst_Property);
    }
    // We don't always fully optimize the blend formula (e.g., for opaque src-over), so we include
    // an "IfOpaque" variant to help set AnalysisProperties::kUnaffectedByDstValue in those cases.
    bool unaffectedByDstIfOpaque() const
    {
        return SkToBool(fProps & kUnaffectedByDstIfOpaque_Property);
    }
    bool usesInputColor() const
    {
        return SkToBool(fProps & kUsesInputColor_Property);
    }
    bool canTweakAlphaForCoverage() const
    {
        return SkToBool(fProps & kCanTweakAlphaForCoverage_Property);
    }

    skgpu::BlendEquation equation() const
    {
        return static_cast<skgpu::BlendEquation>(fBlendEquation);
    }

    skgpu::BlendCoeff srcCoeff() const
    {
        return static_cast<skgpu::BlendCoeff>(fSrcCoeff);
    }

    skgpu::BlendCoeff dstCoeff() const
    {
        return static_cast<skgpu::BlendCoeff>(fDstCoeff);
    }

    OutputType primaryOutput() const
    {
        return fPrimaryOutputType;
    }

    OutputType secondaryOutput() const
    {
        return fSecondaryOutputType;
    }

private:
    enum Properties {
        kModifiesDst_Property = 1 << 0,
        kUnaffectedByDst_Property = 1 << 1,
        kUnaffectedByDstIfOpaque_Property = 1 << 2,
        kUsesInputColor_Property = 1 << 3,
        kCanTweakAlphaForCoverage_Property = 1 << 4,

        kLast_Property = kCanTweakAlphaForCoverage_Property
    };
    SK_DECL_BITFIELD_OPS_FRIENDS(Properties)

    /* *
     * Deduce the properties of a BlendFormula.
     */
    constexpr BlendFormula::Properties GetProperties(OutputType PrimaryOut, OutputType SecondaryOut,
        skgpu::BlendEquation BlendEquation, skgpu::BlendCoeff SrcCoeff, skgpu::BlendCoeff DstCoeff)
    {
        return
            // The provided formula should already be optimized before a BlendFormula is constructed.
            // Assert that here while setting up the properties in the constexpr constructor.
            SkASSERT((kNone_OutputType == PrimaryOut) == !skgpu::BlendCoeffsUseSrcColor(SrcCoeff, DstCoeff)),
            SkASSERT(!skgpu::BlendCoeffRefsSrc2(SrcCoeff)),
            SkASSERT((kNone_OutputType == SecondaryOut) == !skgpu::BlendCoeffRefsSrc2(DstCoeff)),
            SkASSERT(PrimaryOut != SecondaryOut || kNone_OutputType == PrimaryOut),
            SkASSERT(kNone_OutputType != PrimaryOut || kNone_OutputType == SecondaryOut),

            static_cast<Properties>(
            (skgpu::BlendModifiesDst(BlendEquation, SrcCoeff, DstCoeff) ? kModifiesDst_Property : 0) |
            (!skgpu::BlendCoeffsUseDstColor(SrcCoeff, DstCoeff, false /* srcColorIsOpaque */) ?
            kUnaffectedByDst_Property :
            0) |
            (!skgpu::BlendCoeffsUseDstColor(SrcCoeff, DstCoeff, true /* srcColorIsOpaque */) ?
            kUnaffectedByDstIfOpaque_Property :
            0) |
            ((PrimaryOut >= kModulate_OutputType && skgpu::BlendCoeffsUseSrcColor(SrcCoeff, DstCoeff)) ||
            (SecondaryOut >= kModulate_OutputType && skgpu::BlendCoeffRefsSrc2(DstCoeff)) ?
            kUsesInputColor_Property :
            0) | // We assert later that SrcCoeff doesn't ref src2.
            ((kModulate_OutputType == PrimaryOut || kNone_OutputType == PrimaryOut) &&
            kNone_OutputType == SecondaryOut && skgpu::BlendAllowsCoverageAsAlpha(BlendEquation, SrcCoeff, DstCoeff) ?
            kCanTweakAlphaForCoverage_Property :
            0));
    }

    struct {
        // We allot the enums one more bit than they require because MSVC seems to sign-extend
        // them when the top bit is set. (This is in violation of the C++03 standard 9.6/4)
        OutputType fPrimaryOutputType : 4;
        OutputType fSecondaryOutputType : 4;
        uint32_t fBlendEquation : 6;
        uint32_t fSrcCoeff : 6;
        uint32_t fDstCoeff : 6;
        Properties fProps : 32 - (4 + 4 + 6 + 6 + 6);
    };

    static_assert(kLast_OutputType < (1 << 3));
    static_assert(static_cast<int>(skgpu::BlendEquation::kLast) < (1 << 5));
    static_assert(static_cast<int>(skgpu::BlendCoeff::kLast) < (1 << 5));
    static_assert(kLast_Property < (1 << 6));
};

static_assert(4 == sizeof(BlendFormula));

SK_MAKE_BITFIELD_OPS(BlendFormula::Properties)

BlendFormula GetBlendFormula(bool isOpaque, bool hasCoverage, SkBlendMode xfermode);

BlendFormula GetLCDBlendFormula(SkBlendMode xfermode);
} // namespace skgpu

#endif // skgpu_BlendFormula_DEFINED
